#include <cassert>

#include "tm_tmd2_export_target.h"

//TM2 export target
//TEngine model/scene file format

struct TTM2FilteringVert: TTM2Vertex
{
	//weighting
	float Values[3];
	int Influences[3];

	TTM2FilteringVert()
	{
		memset(Values, 0, sizeof(Values));
		memset(Influences, 0, sizeof(Influences));
	}
};

TTMD2ExportTarget::TTMD2ExportTarget()
{
	Skin = 0;
}

TTMD2ExportTarget::~TTMD2ExportTarget()
{
	if (Skin)
		delete Skin;
}

MStatus TTMD2ExportTarget::GetOutputPath(MString& target)
{
	MStringArray ResultArray;
	
	MStatus stat = MGlobal::executeCommand("fileDialog2 -fm 0 -ds 1 -dir \".\" -ff \"TM2 (*.tm2)\"", ResultArray, true, false);

	target = ResultArray[0];

	return stat;
}

MStatus TTMD2ExportTarget::SaveFile(MString filename)
{
	MStatus stat;

	Compile();

	if (!Data.WriteToFile(filename.asChar()))
	{
		stat.perror("ERROR: Could not write file " + filename);
		return stat;
	}
	else
	{
		cout << "Saved TM2 file to " << filename.asChar() << endl;

		stat = MS::kSuccess;	
		return stat;
	}
}

bool CompareFloatv(const float f[], const float f2[], int count)
{
	int diff = 0;

	for (int i=0; i<count; ++i)
	{
		if (f[i] != f2[i]) //!?!?! well whatever it works atm
			++diff;
	}

	return (!diff);
}

bool CompareIntv(const int i1[], const int i2[], int count)
{
	int diff = 0;

	for (int i=0; i<count; ++i)
	{
		if (i1[i] != i2[i])
			++diff;
	}

	return (!diff);
}

int FilterVerts(const TTM2FilteringVert v1, std::vector<TTM2FilteringVert>& target)
{
	//if this vert is already in the array get its index
	if (!target.empty())
	for (int i=0; i<target.size(); ++i)
	{
		//if (CompareVertex(target[i], v1))
		if (CompareFloatv(target[i].Normal, v1.Normal, 3)
			&& CompareFloatv(target[i].Position, v1.Position, 3)
			&& CompareFloatv(target[i].TexCoords, v1.TexCoords, 2)
			&& CompareFloatv(target[i].Tangent, v1.Tangent, 2)
			&& CompareFloatv(target[i].Values, v1.Values, 3)
			&& CompareIntv(target[i].Influences, v1.Influences, 3))
		{
			return i;
		}
	}

	//otherwise add it and return its index
	target.push_back(v1);
	return (int)target.size() - 1;
}

int FilterCollisionVerts(TTM2CollisionVertex v1, std::vector<TTM2CollisionVertex>& target)
{
	//if this vert is already in the array get its index
	if (!target.empty())
	for (int i=0; i<target.size(); ++i)
		//if (CompareVertex(target[i], v1))
		if (CompareFloatv(target[i].Position, v1.Position, 3))
			return i;

	//otherwise add it and return its index
	target.push_back(v1);
	return (int)target.size() - 1;
}

void TTMD2ExportTarget::CompilePolyMeshes()
{
	cout << "Compiling vertices in " << PolyMeshes.size() << " meshes..." << endl;

	//prune the mesh list: if we have a skin, only accept the skinned mesh
	if (Skin && PolyMeshes.size() > 1)
	{
		auto mesh_it = PolyMeshes.begin();

		while (mesh_it != PolyMeshes.end())
		{
			if (mesh_it->Name != Skin->Name)
			{
				cout <<"Warning: Dropped mesh " <<mesh_it->Name << ", not skinned." <<endl;
					
				mesh_it = PolyMeshes.erase(mesh_it);
			}
			else
				++mesh_it;
		}

		assert (!PolyMeshes.empty());
	}
	
	//reserve memory for compiled mesh structures
	Data.Header.PolyMeshCount = (unsigned int)PolyMeshes.size();
	Data.PolyMeshes = new TTM2PolyMesh[PolyMeshes.size()];
	
	//Compile the Polymesh vertex list
	for (int meshno=0; meshno != PolyMeshes.size(); ++meshno)
	{
		cout << " *Mesh #" << meshno << ": " << PolyMeshes[meshno].Triangles.size() << " triangles." << endl;
		
		std::vector<TTM2FilteringVert> MeshVerts;
		
		for (int trino=0; trino != PolyMeshes[meshno].Triangles.size(); ++trino)
		{
			for (int trivert=0; trivert < 3; ++trivert)
			{
				TTM2FilteringVert thisvert;

				int vertidx = PolyMeshes[meshno].Triangles[trino].vertex_idx[trivert];
				int normidx = PolyMeshes[meshno].Triangles[trino].normal_idx[trivert];
				int tanidx = PolyMeshes[meshno].Triangles[trino].tangent_idx[trivert];
				int uvidx = PolyMeshes[meshno].Triangles[trino].uv_idx[trivert];

				thisvert.Position[0] = (float)PolyMeshes[meshno].Vertices[vertidx].x;
				thisvert.Position[1] = (float)PolyMeshes[meshno].Vertices[vertidx].y;
				thisvert.Position[2] = (float)PolyMeshes[meshno].Vertices[vertidx].z;

				thisvert.Normal[0] = PolyMeshes[meshno].Normals[normidx].x;
				thisvert.Normal[1] = PolyMeshes[meshno].Normals[normidx].y;
				thisvert.Normal[2] = PolyMeshes[meshno].Normals[normidx].z;

				thisvert.Tangent[0] = PolyMeshes[meshno].Tangents[tanidx].x;
				thisvert.Tangent[1] = PolyMeshes[meshno].Tangents[tanidx].y;
				thisvert.Tangent[2] = PolyMeshes[meshno].Tangents[tanidx].z;

				thisvert.TexCoords[0] = PolyMeshes[meshno].U_Coords[uvidx];
				thisvert.TexCoords[1] = PolyMeshes[meshno].V_Coords[uvidx];

				if (Skin)
				{
					thisvert.Influences[0] = Skin->Weights[vertidx].Influences[0];
					thisvert.Influences[1] = Skin->Weights[vertidx].Influences[1];
					thisvert.Influences[2] = Skin->Weights[vertidx].Influences[2];

					thisvert.Values[0] = Skin->Weights[vertidx].Values[0];
					thisvert.Values[1] = Skin->Weights[vertidx].Values[1];
					thisvert.Values[2] = Skin->Weights[vertidx].Values[2];
				}

				MeshVerts.push_back(thisvert);
			}//each vert end
		}//each tri end

		std::vector<TTM2FilteringVert> FilteredMeshVerts;
		std::vector<TTM2Index> MeshIndices;

		for (int i = 0; i < MeshVerts.size(); ++i)
			MeshIndices.push_back(FilterVerts(MeshVerts[i], FilteredMeshVerts));

		int difference = (int)(MeshVerts.size() - FilteredMeshVerts.size());
		cout << "Filtered " << difference << " duplicate mesh vertices." << std::endl;

		//it's pretty horrible that we have to do it like this... but fill out the weighting info here
		for (auto vert_it = 0U;
			vert_it < FilteredMeshVerts.size();
			++vert_it)
		{
			TMayaExportWeight filteredweight;
			
			filteredweight.Influences[0] = FilteredMeshVerts[vert_it].Influences[0];
			filteredweight.Influences[1] = FilteredMeshVerts[vert_it].Influences[1];
			filteredweight.Influences[2] = FilteredMeshVerts[vert_it].Influences[2];

			filteredweight.Values[0] = FilteredMeshVerts[vert_it].Values[0];
			filteredweight.Values[1] = FilteredMeshVerts[vert_it].Values[1];
			filteredweight.Values[2] = FilteredMeshVerts[vert_it].Values[2];

			FilteredWeights.push_back(filteredweight);
		}

		//set up the header of the file to be written
		Data.PolyMeshes[meshno].IndexCount = (unsigned int)MeshIndices.size();
		Data.PolyMeshes[meshno].VertexCount = (unsigned int)FilteredMeshVerts.size();

		//alloc memory for the data
		Data.PolyMeshes[meshno].Indices = new TTM2Index[Data.PolyMeshes[meshno].IndexCount];
		Data.PolyMeshes[meshno].Vertices = new TTM2Vertex[Data.PolyMeshes[meshno].VertexCount];
		
		//copy the data to the file's Data structure
		for (unsigned int i=0; i<Data.PolyMeshes[meshno].IndexCount; ++i)
			Data.PolyMeshes[meshno].Indices[i] = MeshIndices[i];
			
		for (unsigned int i=0; i<Data.PolyMeshes[meshno].VertexCount; ++i)
			Data.PolyMeshes[meshno].Vertices[i] = FilteredMeshVerts[i];
	}//each mesh end
}

void TTMD2ExportTarget::CompileCollisionMeshes()
{
	//Compile the Collision vertex list
	cout << "Compiling vertices in " << CollisionMeshes.size() << " collision meshes..." << endl;
	
	//reserve memory for compiled mesh structures
	Data.Header.CollisionMeshCount = (unsigned int)CollisionMeshes.size();
	Data.CollisionMeshes = new TTM2CollisionMesh[CollisionMeshes.size()];
	
	//Compile the Polymesh vertex list
	for (int meshno=0; meshno != CollisionMeshes.size(); ++meshno)
	{
		cout << " *Collision mesh #" << meshno << ": " << CollisionMeshes[meshno].Triangles.size() << " triangles." << endl;
		
		std::vector<TTM2CollisionVertex> MeshVerts;
		
		for (int trino=0; trino != CollisionMeshes[meshno].Triangles.size(); ++trino)
		{
			for (int trivert=0; trivert < 3; ++trivert)
			{
				TTM2CollisionVertex thisvert;

				int vertidx = CollisionMeshes[meshno].Triangles[trino].vertex_idx[trivert];

				thisvert.Position[0] = (float)CollisionMeshes[meshno].Vertices[vertidx].x;
				thisvert.Position[1] = (float)CollisionMeshes[meshno].Vertices[vertidx].y;
				thisvert.Position[2] = (float)CollisionMeshes[meshno].Vertices[vertidx].z;

				MeshVerts.push_back(thisvert);
			}//each vert end
		}//each tri end

		std::vector<TTM2CollisionVertex> FilteredMeshVerts;
		std::vector<TTM2Index> MeshIndices;

		for (int i = 0; i < MeshVerts.size(); ++i)
			MeshIndices.push_back(FilterCollisionVerts(MeshVerts[i], FilteredMeshVerts));

		//set up the header of the file to be written
		Data.CollisionMeshes[meshno].IndexCount = (unsigned int)MeshIndices.size();
		Data.CollisionMeshes[meshno].VertexCount = (unsigned int)FilteredMeshVerts.size();

		//alloc memory for the data
		Data.CollisionMeshes[meshno].Indices = new TTM2Index[Data.CollisionMeshes[meshno].IndexCount];
		Data.CollisionMeshes[meshno].Vertices = new TTM2CollisionVertex[Data.CollisionMeshes[meshno].VertexCount];
		
		//copy the data to the file's Data structure
		for (unsigned int i=0; i<Data.CollisionMeshes[meshno].IndexCount; ++i)
			Data.CollisionMeshes[meshno].Indices[i] = MeshIndices[i];
			
		for (unsigned int i=0; i<Data.CollisionMeshes[meshno].VertexCount; ++i)
			Data.CollisionMeshes[meshno].Vertices[i] = FilteredMeshVerts[i];
	}//each mesh end
}

void TTMD2ExportTarget::CompileLocators()
{
	Data.Header.Flags |= TTM2Header::TM2_LOCATORS;

	Data.LocatorCount = (unsigned int)Locators.size();
	Data.Locators = new TTM2Locator[Data.LocatorCount];

	for (unsigned int loc_it = 0;
		loc_it < Data.LocatorCount;
		++loc_it)
	{
		Data.Locators[loc_it].Name = Locators[loc_it].Name.asChar();
		Locators[loc_it].Position.get(Data.Locators[loc_it].Position);
	}
}

void TTMD2ExportTarget::CompileSkin()
{
	Data.Header.Flags |= TTM2Header::TM2_SKIN;

	Data.JointCount = static_cast<unsigned int>(Skin->Joints[0].size());
	Data.FrameCount = static_cast<unsigned int>(Skin->Joints.size());
	Data.Joints = new TTM2Joint[Data.JointCount * Data.FrameCount];

	if (Data.FrameCount > 1)
		Data.Header.Flags |= TTM2Header::TM2_FRAMES;

	for (unsigned int frame_it = 0;
		frame_it < Data.FrameCount;
		++frame_it)
	{
		for (unsigned int joint_it = 0;
			joint_it < Data.JointCount;
			++joint_it)
		{
			auto& exported_joint = Skin->Joints[frame_it][joint_it];
			
			//float mat[4][4];
			//exported_joint.Transform.get(mat);

			auto& datajoint = Data.Joints[(frame_it * Data.JointCount) + joint_it];

			/*for (int x=0; x<4; ++x) for (int y=0; y<4; ++y)
			{
				datajoint.Matrix[y*4 + x] = mat[x][y];
			}*/
			exported_joint.Position.get(datajoint.Position);

			double rot[4];
			exported_joint.Rotation.get(rot);

			for (int i=0; i<4; ++i)
				datajoint.Rotation[i] = static_cast<float>(rot[i]);

			datajoint.Parent = exported_joint.Parent;
			datajoint.Name = exported_joint.Name.asChar();
		}
	}

	if (Data.PolyMeshes)
	{	
		//std::vector<TTM2Weight>(Data.PolyMeshes[0].VertexCount);

		//unsigned int WeightCount = Data.PolyMeshes[0].VertexCount;
		//Data.Weights = new TTM2Weight[WeightCount];

		Data.Weights = new TTM2Weight[FilteredWeights.size()];

		for (auto weight_it = 0U;
			weight_it < FilteredWeights.size();
			++weight_it)
		{
			Data.Weights[weight_it].Influences[0] = FilteredWeights[weight_it].Influences[0];
			Data.Weights[weight_it].Influences[1] = FilteredWeights[weight_it].Influences[1];
			Data.Weights[weight_it].Influences[2] = FilteredWeights[weight_it].Influences[2];

			Data.Weights[weight_it].Values[0] = FilteredWeights[weight_it].Values[0];
			Data.Weights[weight_it].Values[1] = FilteredWeights[weight_it].Values[1];
			Data.Weights[weight_it].Values[2] = FilteredWeights[weight_it].Values[2];
		}

		/*for (unsigned int weight_it = 0;
			weight_it < WeightCount;
			++weight_it)
		{
			memcpy(Data.Weights[weight_it].Influences, Skin->Weights[weight_it].Joints, sizeof(int) * 3);
			memcpy(Data.Weights[weight_it].Values, Skin->Weights[weight_it].Values, sizeof(float) * 3);
		}*/
	}
}

void TTMD2ExportTarget::Compile()
{
	if (CollisionMeshes.size())
		CompileCollisionMeshes();

	if (PolyMeshes.size())
		CompilePolyMeshes();

	if (!Locators.empty())
		CompileLocators();

	if (Skin)
	{
		CompileSkin();
	}
}

void TTMD2ExportTarget::AddMesh(TMayaExportMesh& mesh)
{
	PolyMeshes.push_back(mesh);
}

void TTMD2ExportTarget::AddCollisionMesh(TMayaBaseMesh& mesh)
{
	CollisionMeshes.push_back(mesh);
}

void TTMD2ExportTarget::AddLocator(TMayaExportLocator& locator)
{
	Locators.push_back(locator);
}

void TTMD2ExportTarget::AddSkin(TMayaExportSkin& skin)
{	
	if (!Skin)
	{
		Skin = new TMayaExportSkin;
		*Skin = skin;
	}
	else
	{
		cout <<"Warning: TMD2 exporter does not support more than one skin cluster per scene (failed to add " <<skin.Name <<")" <<endl;
	}
}

